package com.mini.cn.goods.io.netty.io;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

/**
 * Created with IntelliJ IDEA.
 * User: fly
 * Date: 2020-01-16
 * Time: 5:03 下午
 */
public abstract class NettyServer implements IFourLayerServer {

    protected final InternalLogger logger;
    protected final EventLoopGroup bossGroup;
    protected final EventLoopGroup workGroup;
    protected final ServerBootstrap bootstrap;
    protected IFourLayerServerStateListener serverStateListener;

    public NettyServer() {
        //(bind指定的port，开始侦听和接受客户端链接（如果系统只有一个服务端port需要监听，则BossGroup线程组线程数设置为1)
        bossGroup = new NioEventLoopGroup(1);
        workGroup = new NioEventLoopGroup();
        bootstrap = new ServerBootstrap();
        logger = InternalLoggerFactory.getInstance(getClass());
    }

    /**
     * 设置对应服务handle
     *
     * @return
     */
    public abstract ChannelInitializer<NioSocketChannel> initChannelInitializer();

    @Override
    public void addServiceListener(IFourLayerServerStateListener listener) {
        this.serverStateListener = listener;
    }


    @Override
    public IFourLayerServer init() {

        bootstrap.group(bossGroup, workGroup)
                .channel(NioServerSocketChannel.class)
                //TCP 的连接超时时间 default 30s
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
                //back log 的数字 default 128
                .option(ChannelOption.SO_BACKLOG, 2048)
                //需要根据推送消息的大小，合理设置，对于海量长连接，通常32K是个不错的选择。
                .option(ChannelOption.SO_RCVBUF, 32 * 1024)
                .option(ChannelOption.SO_SNDBUF, 32 * 1024)
                //容量动态调整的接收缓冲区分配器 以节约内存
                .childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator())
                // PooledByteBufAllocator，
                // netty会帮你复用（无需release，除非你后面还需要用到同一个bytebuf)
                // 而不是每次都重新分配ByteBuf。在IO操作中，分配直接内存而不是JVM的堆空间，
                // 就避免了在发送数据时，从JVM到直接内存的拷贝过程，这也就是zero copy的含义。
                .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                //Netty参数，一个Loop写操作执行的最大次数，默认值为16。也就是说，
                // 对于大数据量的写操作至多进行16次，如果16次仍没有全部写完数据，
                // 此时会提交一个新的写任务给EventLoop，任务将在下次调度继续执行。
                // 这样，其他的写请求才能被响应不会因为单个大数据量写请求而耽误。
                .childOption(ChannelOption.WRITE_SPIN_COUNT, 16)
                //Netty参数，写高水位标记，默认值64KB。
                // 如果Netty的写缓冲区中的字节超过该值，Channel的isWritable()返回False。
                //Netty参数，写低水位标记，默认值32KB。当Netty的写缓冲区中的字节超过高水位之后若下降到低水位，则Channel的isWritable()返回True。
                // 写高低水位标记使用户可以控制写入数据速度，从而实现流量控制。推荐做法是：每次调用channl.write(msg)方法首先调用channel.isWritable()判断是否可写。
                .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(516 * 1024, 1024 * 1024))
                //是否启用Nagle算法 就是延时发送
                .childOption(ChannelOption.TCP_NODELAY, true)
                //缺省两个小时的心跳检测
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                //Socket参数，关闭Socket的延迟时间，默认值为-1，表示禁用该功能。
                // -1表示socket.close()方法立即返回，但OS底层会将发送缓冲区全部发送到对端。
                // 0表示socket.close()方法立即返回，OS放弃发送缓冲区的数据直接向对端发送RST包，对端收到复位错误。
                // 非0整数值表示调用socket.close()方法的线程被阻塞直到延迟时间到或发送缓冲区中的数据发送完毕，若超时，则对端会收到复位错误。
                .childOption(ChannelOption.SO_LINGER, -1)
                //Netty参数，一个连接的远端关闭时本地端是否关闭，默认值为False。
                // 值为False时，连接自动关闭；
                // 为True时，触发ChannelInboundHandler的userEventTriggered()方法，事件为ChannelInputShutdownEvent。
                .childOption(ChannelOption.ALLOW_HALF_CLOSURE, false)
                //这个参数表示允许重复使用本地地址和端口，
                //比如，某个服务器进程占用了TCP的80端口进行监听，
                // 此时再次监听该端口就会返回错误，使用该参数就可以解决问题，
                // 该参数允许共用该端口，这个在服务器程序中比较常使用，
                // 比如某个进程非正常退出，该程序占用的端口可能要被占用一段时间才能允许其他进程使用，
                // 而且程序死掉以后，内核一需要一定的时间才能够释放此端口，不设置SO_REUSEADDR
                .childOption(ChannelOption.SO_REUSEADDR, true)
                .handler(new LoggingHandler(LogLevel.DEBUG))
                .childHandler(initChannelInitializer());
        return this;
    }

    @Override
    public void start() {
        ChannelFuture channelFuture;
        try {
            if (serverStateListener != null) {
                serverStateListener.onServiceOnReady(new NettyConfig(bootstrap.config()));
            }
            //同步绑定端口
            channelFuture = bootstrap.bind(getPort()).sync();
            channelFuture.addListener(future -> {
                if (future.isSuccess()) {
                    if (serverStateListener != null) {
                        serverStateListener.onServiceOnStart(getPort());
                    }
                } else {
                    if (serverStateListener != null) {
                        serverStateListener.onServiceOnStop(getPort());
                    }
                }
            });
            //同步等待关闭
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                if (serverStateListener != null) {
                    serverStateListener.onServiceOnStop(getPort());
                }
                shutDown();
            }));
        } catch (Exception e) {
            logger.error(e);
        }
    }

    @Override
    public void shutDown() {
        if (bossGroup != null) {
            bossGroup.shutdownGracefully().syncUninterruptibly();
        }
        if (workGroup != null) {
            workGroup.shutdownGracefully().syncUninterruptibly();
        }
    }
}
